<!DOCTYPE html>
<html CN>







<head>
	
	
	<link rel="stylesheet" href="/css/allinone.min.css"> 

	
	<!-- Global Site Tag (gtag.js) - Google Analytics -->
	<script async src="https://www.googletagmanager.com/gtag/js?id=UA-42863699-1"></script>
	<script>
		window.dataLayer = window.dataLayer || [];
		function gtag(){dataLayer.push(arguments);}
		gtag('js', new Date());
		gtag('config', 'UA-42863699-1');
	</script>
	

	<meta charset="utf-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=edge" />

	<title>flask 源码解析：上下文 | Cizixs Write Here</title>

	<meta name="HandheldFriendly" content="True" />
	<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
	<meta name="generator" content="hexo">
	<meta name="author" content="Cizixs Wu">
	<meta name="description" content="">

	
	<meta name="keywords" content="">
	

	
	<link rel="shortcut icon" href="https://cizixs-blog.oss-cn-beijing.aliyuncs.com/006tNc79ly1g1qxfovpzyj30740743yg.jpg">
	

	
	<meta name="theme-color" content="#3c484e">
	<meta name="msapplication-TileColor" content="#3c484e">
	

	

	

	<meta property="og:site_name" content="Cizixs Write Here">
	<meta property="og:type" content="article">
	<meta property="og:title" content="flask 源码解析：上下文 | Cizixs Write Here">
	<meta property="og:description" content="">
	<meta property="og:url" content="http://cizixs.com/2017/01/13/flask-insight-context/">

	
	<meta property="article:published_time" content="2017-01-13T00:01:00+08:00"/> 
	<meta property="article:author" content="Cizixs Wu">
	<meta property="article:published_first" content="Cizixs Write Here, /2017/01/13/flask-insight-context/" />
	

	
	
	<script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
	

	
	<script src="https://cdn.staticfile.org/highlight.js/9.10.0/highlight.min.js"></script>
	

	
	
<link rel="stylesheet" href="/css/prism-base16-ateliersulphurpool.light.css" type="text/css"></head>
<body class="post-template">
    <div class="site-wrapper">
        




<header class="site-header outer" style="z-index: 999">
    <div class="inner">
        
<nav class="site-nav"> 
    <div class="site-nav-left">
        <ul class="nav">
            <li>
                
                <a href="/" title="Home">Home</a>
                
            </li>
            
            
            <li>
                <a href="/about" title="About">About</a>
            </li>
            
            <li>
                <a href="/archives" title="Archives">Archives</a>
            </li>
            
            
        </ul> 
    </div>
    <div class="site-nav-right">
        
<div class="social-links" >
    
    <a class="social-link" title="weibo" href="https://weibo.com/1921727853" target="_blank" rel="noopener">
        <svg viewBox="0 0 1141 1024" xmlns="http://www.w3.org/2000/svg"><path d="M916.48 518.144q27.648 21.504 38.912 51.712t9.216 62.976-14.336 65.536-31.744 59.392q-34.816 48.128-78.848 81.92t-91.136 56.32-94.72 35.328-89.6 18.944-75.264 7.68-51.712 1.536-49.152-2.56-68.096-10.24-78.336-21.504-79.872-36.352-74.24-55.296-59.904-78.848q-16.384-29.696-22.016-63.488t-5.632-86.016q0-22.528 7.68-51.2t27.136-63.488 53.248-75.776 86.016-90.112q51.2-48.128 105.984-85.504t117.248-57.856q28.672-10.24 63.488-11.264t57.344 11.264q10.24 11.264 19.456 23.04t12.288 29.184q3.072 14.336 0.512 27.648t-5.632 26.624-5.12 25.6 2.048 22.528q17.408 2.048 33.792-1.536t31.744-9.216 31.232-11.776 33.28-9.216q27.648-5.12 54.784-4.608t49.152 7.68 36.352 22.016 17.408 38.4q2.048 14.336-2.048 26.624t-8.704 23.04-7.168 22.016 1.536 23.552q3.072 7.168 14.848 13.312t27.136 12.288 32.256 13.312 29.184 16.384zM658.432 836.608q26.624-16.384 53.76-45.056t44.032-64 18.944-75.776-20.48-81.408q-19.456-33.792-47.616-57.344t-62.976-37.376-74.24-19.968-80.384-6.144q-78.848 0-139.776 16.384t-105.472 43.008-72.192 60.416-38.912 68.608q-11.264 33.792-6.656 67.072t20.992 62.976 42.496 53.248 57.856 37.888q58.368 25.6 119.296 32.256t116.224 0.512 100.864-21.504 74.24-33.792zM524.288 513.024q20.48 8.192 38.912 18.432t32.768 27.648q10.24 12.288 17.92 30.72t10.752 39.424 1.536 42.496-9.728 38.912q-8.192 18.432-19.968 37.376t-28.672 35.328-40.448 29.184-57.344 18.944q-61.44 11.264-117.76-11.264t-88.064-74.752q-12.288-39.936-13.312-70.656t16.384-66.56q13.312-27.648 40.448-51.712t62.464-38.912 75.264-17.408 78.848 12.8zM361.472 764.928q37.888 3.072 57.856-18.432t21.504-48.128-15.36-47.616-52.736-16.896q-27.648 3.072-43.008 23.552t-17.408 43.52 9.728 42.496 39.424 21.504zM780.288 6.144q74.752 0 139.776 19.968t113.664 57.856 76.288 92.16 27.648 122.88q0 33.792-16.384 50.688t-35.328 17.408-35.328-14.336-16.384-45.568q0-40.96-22.528-77.824t-59.392-64.512-84.48-43.52-96.768-15.872q-31.744 0-47.104-15.36t-14.336-34.304 18.944-34.304 51.712-15.36zM780.288 169.984q95.232 0 144.384 48.64t49.152 146.944q0 30.72-10.24 43.52t-22.528 11.264-22.528-14.848-10.24-35.84q0-60.416-34.816-96.256t-93.184-35.84q-19.456 0-28.672-10.752t-9.216-23.04 9.728-23.04 28.16-10.752z" /></svg>
    </a>
    

    
    <a class="social-link" title="github" href="https://github.com/cizixs" target="_blank" rel="noopener">
        <svg viewBox="0 0 1049 1024" xmlns="http://www.w3.org/2000/svg"><path d="M524.979332 0C234.676191 0 0 234.676191 0 524.979332c0 232.068678 150.366597 428.501342 358.967656 498.035028 26.075132 5.215026 35.636014-11.299224 35.636014-25.205961 0-12.168395-0.869171-53.888607-0.869171-97.347161-146.020741 31.290159-176.441729-62.580318-176.441729-62.580318-23.467619-60.841976-58.234462-76.487055-58.234463-76.487055-47.804409-32.15933 3.476684-32.15933 3.476685-32.15933 53.019436 3.476684 80.83291 53.888607 80.83291 53.888607 46.935238 79.963739 122.553122 57.365291 152.97411 43.458554 4.345855-33.897672 18.252593-57.365291 33.028501-70.402857-116.468925-12.168395-239.022047-57.365291-239.022047-259.012982 0-57.365291 20.860106-104.300529 53.888607-140.805715-5.215026-13.037566-23.467619-66.926173 5.215027-139.067372 0 0 44.327725-13.906737 144.282399 53.888607 41.720212-11.299224 86.917108-17.383422 131.244833-17.383422s89.524621 6.084198 131.244833 17.383422C756.178839 203.386032 800.506564 217.29277 800.506564 217.29277c28.682646 72.1412 10.430053 126.029806 5.215026 139.067372 33.897672 36.505185 53.888607 83.440424 53.888607 140.805715 0 201.64769-122.553122 245.975415-239.891218 259.012982 19.121764 16.514251 35.636014 47.804409 35.636015 97.347161 0 70.402857-0.869171 126.898978-0.869172 144.282399 0 13.906737 9.560882 30.420988 35.636015 25.205961 208.601059-69.533686 358.967656-265.96635 358.967655-498.035028C1049.958663 234.676191 814.413301 0 524.979332 0z" /></svg>
    </a>
    

    
    <a class="social-link" title="stackoverflow" href="https://stackoverflow.com/users/1925083/cizixs" target="_blank" rel="noopener">
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M15 21h-10v-2h10v2zm6-11.665l-1.621-9.335-1.993.346 1.62 9.335 1.994-.346zm-5.964 6.937l-9.746-.975-.186 2.016 9.755.879.177-1.92zm.538-2.587l-9.276-2.608-.526 1.954 9.306 2.5.496-1.846zm1.204-2.413l-8.297-4.864-1.029 1.743 8.298 4.865 1.028-1.744zm1.866-1.467l-5.339-7.829-1.672 1.14 5.339 7.829 1.672-1.14zm-2.644 4.195v8h-12v-8h-2v10h16v-10h-2z"/></svg>
    </a>
    

    

    
    <a class="social-link" title="twitter" href="https://twitter.com/cizixs" target="_blank" rel="noopener">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M30.063 7.313c-.813 1.125-1.75 2.125-2.875 2.938v.75c0 1.563-.188 3.125-.688 4.625a15.088 15.088 0 0 1-2.063 4.438c-.875 1.438-2 2.688-3.25 3.813a15.015 15.015 0 0 1-4.625 2.563c-1.813.688-3.75 1-5.75 1-3.25 0-6.188-.875-8.875-2.625.438.063.875.125 1.375.125 2.688 0 5.063-.875 7.188-2.5-1.25 0-2.375-.375-3.375-1.125s-1.688-1.688-2.063-2.875c.438.063.813.125 1.125.125.5 0 1-.063 1.5-.25-1.313-.25-2.438-.938-3.313-1.938a5.673 5.673 0 0 1-1.313-3.688v-.063c.813.438 1.688.688 2.625.688a5.228 5.228 0 0 1-1.875-2c-.5-.875-.688-1.813-.688-2.75 0-1.063.25-2.063.75-2.938 1.438 1.75 3.188 3.188 5.25 4.25s4.313 1.688 6.688 1.813a5.579 5.579 0 0 1 1.5-5.438c1.125-1.125 2.5-1.688 4.125-1.688s3.063.625 4.188 1.813a11.48 11.48 0 0 0 3.688-1.375c-.438 1.375-1.313 2.438-2.563 3.188 1.125-.125 2.188-.438 3.313-.875z"/></svg>

    </a>
    

    
    <a class="social-link" title="instagram" href="https://www.instagram.com/cizixs/" target="_blank" rel="noopener">
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zm0 5.838c-3.403 0-6.162 2.759-6.162 6.162s2.759 6.163 6.162 6.163 6.162-2.759 6.162-6.163c0-3.403-2.759-6.162-6.162-6.162zm0 10.162c-2.209 0-4-1.79-4-4 0-2.209 1.791-4 4-4s4 1.791 4 4c0 2.21-1.791 4-4 4zm6.406-11.845c-.796 0-1.441.645-1.441 1.44s.645 1.44 1.441 1.44c.795 0 1.439-.645 1.439-1.44s-.644-1.44-1.439-1.44z"/></svg>
    </a>
    
    
    
</div>
    </div>
</nav>
    </div>
</header>


<main id="site-main" class="site-main outer" role="main">
    <div class="inner">
        <header class="post-full-header">
            <section class="post-full-meta">
                <time  class="post-full-meta-date" datetime="2017-01-12T16:00:00.000Z" itemprop="datePublished">
                    2017-01-13
                </time>
                
                <span class="date-divider">/</span>
                
                <a href="/categories/blog/">blog</a>&nbsp;&nbsp;
                
                
            </section>
            <h1 class="post-full-title">flask 源码解析：上下文</h1>
        </header>
        <article class="post-full no-image">
            
            <section class="post-full-content">
                <div id="lightgallery" class="markdown-body">
                    <p>这是 flask 源码解析系列文章的其中一篇，本系列所有文章列表：</p>
<ul>
<li><a href="http://cizixs.com/2017/01/10/flask-insight-introduction">flask 源码解析：简介</a></li>
<li><a href="http://cizixs.com/2017/01/11/flask-insight-start-process">flask 源码解析：应用启动流程</a></li>
<li><a href="http://cizixs.com/2017/01/12/flask-insight-routing">flask 源码解析：路由</a></li>
<li><a href="http://cizixs.com/2017/01/13/flask-insight-context">flask 源码解析：上下文</a></li>
<li><a href="http://cizixs.com/2017/01/18/flask-insight-request">flask 源码解析：请求</a></li>
<li><a href="http://cizixs.com/2017/01/22/flask-insight-response">flask 源码解析：响应</a></li>
<li><a href="http://cizixs.com/2017/03/08/flask-insight-session">flask 源码解析：session</a></li>
</ul>
<h2 id="上下文（application-context-和-request-context）"><a href="#上下文（application-context-和-request-context）" class="headerlink" title="上下文（application context 和 request context）"></a>上下文（application context 和 request context）</h2><p>上下文一直是计算机中难理解的概念，在<a href="https://www.zhihu.com/question/26387327" target="_blank" rel="noopener">知乎的一个问题</a>下面有个很通俗易懂的回答：</p>
<blockquote>
<p>每一段程序都有很多外部变量。只有像Add这种简单的函数才是没有外部变量的。一旦你的一段程序有了外部变量，这段程序就不完整，不能独立运行。你为了使他们运行，就要给所有的外部变量一个一个写一些值进去。这些值的集合就叫上下文。<br>– vzch</p>
</blockquote>
<p>比如，在 flask 中，视图函数需要知道它执行情况的请求信息（请求的 url，参数，方法等）以及应用信息（应用中初始化的数据库等），才能够正确运行。</p>
<p>最直观地做法是把这些信息封装成一个对象，作为参数传递给视图函数。但是这样的话，所有的视图函数都需要添加对应的参数，即使该函数内部并没有使用到它。</p>
<p>flask 的做法是把这些信息作为<strong>类似全局变量的东西</strong>，视图函数需要的时候，可以使用 <code>from flask import request</code> 获取。但是这些对象和全局变量不同的是——它们必须是动态的，因为在多线程或者多协程的情况下，每个线程或者协程获取的都是自己独特的对象，不会互相干扰。</p>
<p>那么如何实现这种效果呢？如果对 python 多线程比较熟悉的话，应该知道多线程中有个非常类似的概念 <a href="http://stackoverflow.com/questions/104983/what-is-thread-local-storage-in-python-and-why-do-i-need-it#" target="_blank" rel="noopener"><code>threading.local</code></a>，可以实现多线程访问某个变量的时候只看到自己的数据。内部的原理说起来也很简单，这个对象有一个字典，保存了线程 id 对应的数据，读取该对象的时候，它动态地查询当前线程 id 对应的数据。flaskpython 上下文的实现也类似，后面会详细解释。</p>
<p>flask 中有两种上下文：<code>application context</code> 和 <code>request context</code>。上下文有关的内容定义在 <code>globals.py</code> 文件，文件的内容也非常短：</p>
<pre class=" language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">_lookup_req_object</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">:</span>
    top <span class="token operator">=</span> _request_ctx_stack<span class="token punctuation">.</span>top
    <span class="token keyword">if</span> top <span class="token keyword">is</span> None<span class="token punctuation">:</span>
        <span class="token keyword">raise</span> RuntimeError<span class="token punctuation">(</span>_request_ctx_err_msg<span class="token punctuation">)</span>
    <span class="token keyword">return</span> getattr<span class="token punctuation">(</span>top<span class="token punctuation">,</span> name<span class="token punctuation">)</span>


<span class="token keyword">def</span> <span class="token function">_lookup_app_object</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">:</span>
    top <span class="token operator">=</span> _app_ctx_stack<span class="token punctuation">.</span>top
    <span class="token keyword">if</span> top <span class="token keyword">is</span> None<span class="token punctuation">:</span>
        <span class="token keyword">raise</span> RuntimeError<span class="token punctuation">(</span>_app_ctx_err_msg<span class="token punctuation">)</span>
    <span class="token keyword">return</span> getattr<span class="token punctuation">(</span>top<span class="token punctuation">,</span> name<span class="token punctuation">)</span>


<span class="token keyword">def</span> <span class="token function">_find_app</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
    top <span class="token operator">=</span> _app_ctx_stack<span class="token punctuation">.</span>top
    <span class="token keyword">if</span> top <span class="token keyword">is</span> None<span class="token punctuation">:</span>
        <span class="token keyword">raise</span> RuntimeError<span class="token punctuation">(</span>_app_ctx_err_msg<span class="token punctuation">)</span>
    <span class="token keyword">return</span> top<span class="token punctuation">.</span>app


<span class="token comment" spellcheck="true"># context locals</span>
_request_ctx_stack <span class="token operator">=</span> LocalStack<span class="token punctuation">(</span><span class="token punctuation">)</span>
_app_ctx_stack <span class="token operator">=</span> LocalStack<span class="token punctuation">(</span><span class="token punctuation">)</span>
current_app <span class="token operator">=</span> LocalProxy<span class="token punctuation">(</span>_find_app<span class="token punctuation">)</span>
request <span class="token operator">=</span> LocalProxy<span class="token punctuation">(</span>partial<span class="token punctuation">(</span>_lookup_req_object<span class="token punctuation">,</span> <span class="token string">'request'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
session <span class="token operator">=</span> LocalProxy<span class="token punctuation">(</span>partial<span class="token punctuation">(</span>_lookup_req_object<span class="token punctuation">,</span> <span class="token string">'session'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
g <span class="token operator">=</span> LocalProxy<span class="token punctuation">(</span>partial<span class="token punctuation">(</span>_lookup_app_object<span class="token punctuation">,</span> <span class="token string">'g'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
</code></pre>
<p><code>flask</code> 提供两种上下文：<code>application context</code> 和 <code>request context</code> 。<code>app lication context</code> 又演化出来两个变量 <code>current_app</code> 和 <code>g</code>，而 <code>request context</code> 则演化出来 <code>request</code> 和 <code>session</code>。</p>
<p>这里的实现用到了两个东西：<code>LocalStack</code> 和 <code>LocalProxy</code>。它们两个的结果就是我们可以动态地获取两个上下文的内容，在并发程序中每个视图函数都会看到属于自己的上下文，而不会出现混乱。</p>
<p><code>LocalStack</code> 和 <code>LocalProxy</code> 都是 <code>werkzeug</code> 提供的，定义在 <code>local.py</code> 文件中。在分析这两个类之前，我们先介绍这个文件另外一个基础的类 <code>Local</code>。<code>Local</code> 就是实现了类似 <code>threading.local</code> 的效果——多线程或者多协程情况下全局变量的隔离效果。下面是它的代码：</p>
<pre class=" language-python"><code class="language-python"><span class="token comment" spellcheck="true"># since each thread has its own greenlet we can just use those as identifiers</span>
<span class="token comment" spellcheck="true"># for the context.  If greenlets are not available we fall back to the</span>
<span class="token comment" spellcheck="true"># current thread ident depending on where it is.</span>
<span class="token keyword">try</span><span class="token punctuation">:</span>
    <span class="token keyword">from</span> greenlet <span class="token keyword">import</span> getcurrent <span class="token keyword">as</span> get_ident
<span class="token keyword">except</span> ImportError<span class="token punctuation">:</span>
    <span class="token keyword">try</span><span class="token punctuation">:</span>
        <span class="token keyword">from</span> thread <span class="token keyword">import</span> get_ident
    <span class="token keyword">except</span> ImportError<span class="token punctuation">:</span>
        <span class="token keyword">from</span> _thread <span class="token keyword">import</span> get_ident

<span class="token keyword">class</span> <span class="token class-name">Local</span><span class="token punctuation">(</span>object<span class="token punctuation">)</span><span class="token punctuation">:</span>
    __slots__ <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token string">'__storage__'</span><span class="token punctuation">,</span> <span class="token string">'__ident_func__'</span><span class="token punctuation">)</span>

    <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token comment" spellcheck="true"># 数据保存在 __storage__ 中，后续访问都是对该属性的操作</span>
        object<span class="token punctuation">.</span>__setattr__<span class="token punctuation">(</span>self<span class="token punctuation">,</span> <span class="token string">'__storage__'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span>
        object<span class="token punctuation">.</span>__setattr__<span class="token punctuation">(</span>self<span class="token punctuation">,</span> <span class="token string">'__ident_func__'</span><span class="token punctuation">,</span> get_ident<span class="token punctuation">)</span>

    <span class="token keyword">def</span> <span class="token function">__call__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> proxy<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token triple-quoted-string string">"""Create a proxy for a name."""</span>
        <span class="token keyword">return</span> LocalProxy<span class="token punctuation">(</span>self<span class="token punctuation">,</span> proxy<span class="token punctuation">)</span>

    <span class="token comment" spellcheck="true"># 清空当前线程/协程保存的所有数据</span>
    <span class="token keyword">def</span> <span class="token function">__release_local__</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        self<span class="token punctuation">.</span>__storage__<span class="token punctuation">.</span>pop<span class="token punctuation">(</span>self<span class="token punctuation">.</span>__ident_func__<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> None<span class="token punctuation">)</span>

    <span class="token comment" spellcheck="true"># 下面三个方法实现了属性的访问、设置和删除。</span>
    <span class="token comment" spellcheck="true"># 注意到，内部都调用 `self.__ident_func__` 获取当前线程或者协程的 id，然后再访问对应的内部字典。</span>
    <span class="token comment" spellcheck="true"># 如果访问或者删除的属性不存在，会抛出 AttributeError。</span>
    <span class="token comment" spellcheck="true"># 这样，外部用户看到的就是它在访问实例的属性，完全不知道字典或者多线程/协程切换的实现</span>
    <span class="token keyword">def</span> <span class="token function">__getattr__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> name<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token keyword">try</span><span class="token punctuation">:</span>
            <span class="token keyword">return</span> self<span class="token punctuation">.</span>__storage__<span class="token punctuation">[</span>self<span class="token punctuation">.</span>__ident_func__<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">[</span>name<span class="token punctuation">]</span>
        <span class="token keyword">except</span> KeyError<span class="token punctuation">:</span>
            <span class="token keyword">raise</span> AttributeError<span class="token punctuation">(</span>name<span class="token punctuation">)</span>

    <span class="token keyword">def</span> <span class="token function">__setattr__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> name<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">:</span>
        ident <span class="token operator">=</span> self<span class="token punctuation">.</span>__ident_func__<span class="token punctuation">(</span><span class="token punctuation">)</span>
        storage <span class="token operator">=</span> self<span class="token punctuation">.</span>__storage__
        <span class="token keyword">try</span><span class="token punctuation">:</span>
            storage<span class="token punctuation">[</span>ident<span class="token punctuation">]</span><span class="token punctuation">[</span>name<span class="token punctuation">]</span> <span class="token operator">=</span> value
        <span class="token keyword">except</span> KeyError<span class="token punctuation">:</span>
            storage<span class="token punctuation">[</span>ident<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">{</span>name<span class="token punctuation">:</span> value<span class="token punctuation">}</span>

    <span class="token keyword">def</span> <span class="token function">__delattr__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> name<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token keyword">try</span><span class="token punctuation">:</span>
            <span class="token keyword">del</span> self<span class="token punctuation">.</span>__storage__<span class="token punctuation">[</span>self<span class="token punctuation">.</span>__ident_func__<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">[</span>name<span class="token punctuation">]</span>
        <span class="token keyword">except</span> KeyError<span class="token punctuation">:</span>
            <span class="token keyword">raise</span> AttributeError<span class="token punctuation">(</span>name<span class="token punctuation">)</span>
</code></pre>
<p>可以看到，<code>Local</code> 对象内部的数据都是保存在 <code>__storage__</code> 属性的，这个属性变量是个嵌套的字典：<code>map[ident]map[key]value</code>。最外面字典 key 是线程或者协程的 identity，value 是另外一个字典，这个内部字典就是用户自定义的 key-value 键值对。用户访问实例的属性，就变成了访问内部的字典，外面字典的 key 是自动关联的。<code>__ident_func</code> 是 协程的 <code>get_current</code> 或者线程的 <code>get_ident</code>，从而获取当前代码所在线程或者协程的 id。</p>
<p>除了这些基本操作之外，<code>Local</code> 还实现了 <code>__release_local__</code> ，用来清空（析构）当前线程或者协程的数据（状态）。<code>__call__</code> 操作来创建一个 <code>LocalProxy</code> 对象，<code>LocalProxy</code> 会在下面讲到。</p>
<p>理解了 <code>Local</code>，我们继续回来看另外两个类。</p>
<p><code>LocalStack</code> 是基于 <code>Local</code> 实现的栈结构。如果说 <code>Local</code> 提供了多线程或者多协程隔离的属性访问，那么 <code>LocalStack</code> 就提供了隔离的栈访问。下面是它的实现代码，可以看到它提供了 <code>push</code>、<code>pop</code> 和 <code>top</code> 方法。</p>
<p><code>__release_local__</code> 可以用来清空当前线程或者协程的栈数据，<code>__call__</code> 方法返回当前线程或者协程栈顶元素的代理对象。</p>
<pre class=" language-python"><code class="language-python"><span class="token keyword">class</span> <span class="token class-name">LocalStack</span><span class="token punctuation">(</span>object<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""This class works similar to a :class:`Local` but keeps a stack
    of objects instead. """</span>

    <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        self<span class="token punctuation">.</span>_local <span class="token operator">=</span> Local<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">def</span> <span class="token function">__release_local__</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        self<span class="token punctuation">.</span>_local<span class="token punctuation">.</span>__release_local__<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">def</span> <span class="token function">__call__</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token keyword">def</span> <span class="token function">_lookup</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
            rv <span class="token operator">=</span> self<span class="token punctuation">.</span>top
            <span class="token keyword">if</span> rv <span class="token keyword">is</span> None<span class="token punctuation">:</span>
                <span class="token keyword">raise</span> RuntimeError<span class="token punctuation">(</span><span class="token string">'object unbound'</span><span class="token punctuation">)</span>
            <span class="token keyword">return</span> rv
        <span class="token keyword">return</span> LocalProxy<span class="token punctuation">(</span>_lookup<span class="token punctuation">)</span>

    <span class="token comment" spellcheck="true"># push、pop 和 top 三个方法实现了栈的操作，</span>
    <span class="token comment" spellcheck="true"># 可以看到栈的数据是保存在 self._local.stack 属性中的</span>
    <span class="token keyword">def</span> <span class="token function">push</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> obj<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token triple-quoted-string string">"""Pushes a new item to the stack"""</span>
        rv <span class="token operator">=</span> getattr<span class="token punctuation">(</span>self<span class="token punctuation">.</span>_local<span class="token punctuation">,</span> <span class="token string">'stack'</span><span class="token punctuation">,</span> None<span class="token punctuation">)</span>
        <span class="token keyword">if</span> rv <span class="token keyword">is</span> None<span class="token punctuation">:</span>
            self<span class="token punctuation">.</span>_local<span class="token punctuation">.</span>stack <span class="token operator">=</span> rv <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
        rv<span class="token punctuation">.</span>append<span class="token punctuation">(</span>obj<span class="token punctuation">)</span>
        <span class="token keyword">return</span> rv

    <span class="token keyword">def</span> <span class="token function">pop</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token triple-quoted-string string">"""Removes the topmost item from the stack, will return the
        old value or `None` if the stack was already empty.
        """</span>
        stack <span class="token operator">=</span> getattr<span class="token punctuation">(</span>self<span class="token punctuation">.</span>_local<span class="token punctuation">,</span> <span class="token string">'stack'</span><span class="token punctuation">,</span> None<span class="token punctuation">)</span>
        <span class="token keyword">if</span> stack <span class="token keyword">is</span> None<span class="token punctuation">:</span>
            <span class="token keyword">return</span> None
        <span class="token keyword">elif</span> len<span class="token punctuation">(</span>stack<span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">1</span><span class="token punctuation">:</span>
            release_local<span class="token punctuation">(</span>self<span class="token punctuation">.</span>_local<span class="token punctuation">)</span>
            <span class="token keyword">return</span> stack<span class="token punctuation">[</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span>
        <span class="token keyword">else</span><span class="token punctuation">:</span>
            <span class="token keyword">return</span> stack<span class="token punctuation">.</span>pop<span class="token punctuation">(</span><span class="token punctuation">)</span>

    @property
    <span class="token keyword">def</span> <span class="token function">top</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token triple-quoted-string string">"""The topmost item on the stack.  If the stack is empty,
        `None` is returned.
        """</span>
        <span class="token keyword">try</span><span class="token punctuation">:</span>
            <span class="token keyword">return</span> self<span class="token punctuation">.</span>_local<span class="token punctuation">.</span>stack<span class="token punctuation">[</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span>
        <span class="token keyword">except</span> <span class="token punctuation">(</span>AttributeError<span class="token punctuation">,</span> IndexError<span class="token punctuation">)</span><span class="token punctuation">:</span>
            <span class="token keyword">return</span> None
</code></pre>
<p>我们在之前看到了 <code>request context</code> 的定义，它就是一个 <code>LocalStack</code> 的实例：</p>
<pre class=" language-python"><code class="language-python">_request_ctx_stack <span class="token operator">=</span> LocalStack<span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre>
<p>它会当前线程或者协程的请求都保存在栈里，等使用的时候再从里面读取。至于为什么要用到栈结构，而不是直接使用 <code>Local</code>，我们会在后面揭晓答案，你可以先思考一下。</p>
<p><code>LocalProxy</code> 是一个 <code>Local</code> 对象的代理，负责把所有对自己的操作转发给内部的 <code>Local</code> 对象。<code>LocalProxy</code> 的构造函数介绍一个 callable 的参数，这个 callable 调用之后需要返回一个 <code>Local</code> 实例，后续所有的属性操作都会转发给 callable 返回的对象。</p>
<pre class=" language-python"><code class="language-python"><span class="token keyword">class</span> <span class="token class-name">LocalProxy</span><span class="token punctuation">(</span>object<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""Acts as a proxy for a werkzeug local.
    Forwards all operations to a proxied object. """</span>
    __slots__ <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token string">'__local'</span><span class="token punctuation">,</span> <span class="token string">'__dict__'</span><span class="token punctuation">,</span> <span class="token string">'__name__'</span><span class="token punctuation">)</span>

    <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> local<span class="token punctuation">,</span> name<span class="token operator">=</span>None<span class="token punctuation">)</span><span class="token punctuation">:</span>
        object<span class="token punctuation">.</span>__setattr__<span class="token punctuation">(</span>self<span class="token punctuation">,</span> <span class="token string">'_LocalProxy__local'</span><span class="token punctuation">,</span> local<span class="token punctuation">)</span>
        object<span class="token punctuation">.</span>__setattr__<span class="token punctuation">(</span>self<span class="token punctuation">,</span> <span class="token string">'__name__'</span><span class="token punctuation">,</span> name<span class="token punctuation">)</span>

    <span class="token keyword">def</span> <span class="token function">_get_current_object</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token triple-quoted-string string">"""Return the current object."""</span>
        <span class="token keyword">if</span> <span class="token operator">not</span> hasattr<span class="token punctuation">(</span>self<span class="token punctuation">.</span>__local<span class="token punctuation">,</span> <span class="token string">'__release_local__'</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
            <span class="token keyword">return</span> self<span class="token punctuation">.</span>__local<span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token keyword">try</span><span class="token punctuation">:</span>
            <span class="token keyword">return</span> getattr<span class="token punctuation">(</span>self<span class="token punctuation">.</span>__local<span class="token punctuation">,</span> self<span class="token punctuation">.</span>__name__<span class="token punctuation">)</span>
        <span class="token keyword">except</span> AttributeError<span class="token punctuation">:</span>
            <span class="token keyword">raise</span> RuntimeError<span class="token punctuation">(</span><span class="token string">'no object bound to %s'</span> <span class="token operator">%</span> self<span class="token punctuation">.</span>__name__<span class="token punctuation">)</span>

    @property
    <span class="token keyword">def</span> <span class="token function">__dict__</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token keyword">try</span><span class="token punctuation">:</span>
            <span class="token keyword">return</span> self<span class="token punctuation">.</span>_get_current_object<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>__dict__
        <span class="token keyword">except</span> RuntimeError<span class="token punctuation">:</span>
            <span class="token keyword">raise</span> AttributeError<span class="token punctuation">(</span><span class="token string">'__dict__'</span><span class="token punctuation">)</span>

    <span class="token keyword">def</span> <span class="token function">__getattr__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> name<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token keyword">if</span> name <span class="token operator">==</span> <span class="token string">'__members__'</span><span class="token punctuation">:</span>
            <span class="token keyword">return</span> dir<span class="token punctuation">(</span>self<span class="token punctuation">.</span>_get_current_object<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span> getattr<span class="token punctuation">(</span>self<span class="token punctuation">.</span>_get_current_object<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> name<span class="token punctuation">)</span>

    <span class="token keyword">def</span> <span class="token function">__setitem__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> key<span class="token punctuation">,</span> value<span class="token punctuation">)</span><span class="token punctuation">:</span>
        self<span class="token punctuation">.</span>_get_current_object<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> value
</code></pre>
<p>这里实现的关键是把通过参数传递进来的 <code>Local</code> 实例保存在 <code>__local</code> 属性中，并定义了 <code>_get_current_object()</code> 方法获取当前线程或者协程对应的对象。</p>
<p><strong>NOTE</strong>：前面双下划线的属性，会保存到 <code>_ClassName__variable</code> 中。所以这里通过 <code>“_LocalProxy__local”</code> 设置的值，后面可以通过 <code>self.__local</code> 来获取。关于这个知识点，可以查看 <a href="http://stackoverflow.com/questions/1301346/the-meaning-of-a-single-and-a-double-underscore-before-an-object-name-in-python" target="_blank" rel="noopener">stackoverflow 的这个问题</a>。</p>
<p>然后 <code>LocalProxy</code> 重写了所有的魔术方法（名字前后有两个下划线的方法），具体操作都是转发给代理对象的。这里只给出了几个魔术方法，感兴趣的可以查看源码中所有的魔术方法。</p>
<p>继续回到 <code>request context</code> 的实现：</p>
<pre class=" language-python"><code class="language-python">_request_ctx_stack <span class="token operator">=</span> LocalStack<span class="token punctuation">(</span><span class="token punctuation">)</span>
request <span class="token operator">=</span> LocalProxy<span class="token punctuation">(</span>partial<span class="token punctuation">(</span>_lookup_req_object<span class="token punctuation">,</span> <span class="token string">'request'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
session <span class="token operator">=</span> LocalProxy<span class="token punctuation">(</span>partial<span class="token punctuation">(</span>_lookup_req_object<span class="token punctuation">,</span> <span class="token string">'session'</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
</code></pre>
<p>再次看这段代码希望能看明白，<code>_request_ctx_stack</code> 是多线程或者协程隔离的栈结构，<code>request</code> 每次都会调用 <code>_lookup_req_object</code> 栈头部的数据来获取保存在里面的 <code>requst context</code>。</p>
<p>那么请求上下文信息是什么被放在 stack 中呢？还记得之前介绍的 <code>wsgi_app()</code> 方法有下面两行代码吗？</p>
<pre class=" language-python"><code class="language-python">ctx <span class="token operator">=</span> self<span class="token punctuation">.</span>request_context<span class="token punctuation">(</span>environ<span class="token punctuation">)</span>
ctx<span class="token punctuation">.</span>push<span class="token punctuation">(</span><span class="token punctuation">)</span>
</code></pre>
<p>每次在调用 <code>app.__call__</code> 的时候，都会把对应的请求信息压栈，最后执行完请求的处理之后把它出栈。</p>
<p>我们来看看<code>request_context</code>， 这个 方法只有一行代码：</p>
<pre class=" language-python"><code class="language-python"><span class="token keyword">def</span> <span class="token function">request_context</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> environ<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token keyword">return</span> RequestContext<span class="token punctuation">(</span>self<span class="token punctuation">,</span> environ<span class="token punctuation">)</span>
</code></pre>
<p>它调用了 <code>RequestContext</code>，并把 <code>self</code> 和请求信息的字典 <code>environ</code> 当做参数传递进去。追踪到 <code>RequestContext</code> 定义的地方，它出现在 <code>ctx.py</code> 文件中，代码如下：</p>
<pre class=" language-python"><code class="language-python"><span class="token keyword">class</span> <span class="token class-name">RequestContext</span><span class="token punctuation">(</span>object<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token triple-quoted-string string">"""The request context contains all request relevant information.  It is
    created at the beginning of the request and pushed to the
    `_request_ctx_stack` and removed at the end of it.  It will create the
    URL adapter and request object for the WSGI environment provided.
    """</span>

    <span class="token keyword">def</span> <span class="token function">__init__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> app<span class="token punctuation">,</span> environ<span class="token punctuation">,</span> request<span class="token operator">=</span>None<span class="token punctuation">)</span><span class="token punctuation">:</span>
        self<span class="token punctuation">.</span>app <span class="token operator">=</span> app
        <span class="token keyword">if</span> request <span class="token keyword">is</span> None<span class="token punctuation">:</span>
            request <span class="token operator">=</span> app<span class="token punctuation">.</span>request_class<span class="token punctuation">(</span>environ<span class="token punctuation">)</span>
        self<span class="token punctuation">.</span>request <span class="token operator">=</span> request
        self<span class="token punctuation">.</span>url_adapter <span class="token operator">=</span> app<span class="token punctuation">.</span>create_url_adapter<span class="token punctuation">(</span>self<span class="token punctuation">.</span>request<span class="token punctuation">)</span>
        self<span class="token punctuation">.</span>match_request<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">def</span> <span class="token function">match_request</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token triple-quoted-string string">"""Can be overridden by a subclass to hook into the matching
        of the request.
        """</span>
        <span class="token keyword">try</span><span class="token punctuation">:</span>
            url_rule<span class="token punctuation">,</span> self<span class="token punctuation">.</span>request<span class="token punctuation">.</span>view_args <span class="token operator">=</span> \
                self<span class="token punctuation">.</span>url_adapter<span class="token punctuation">.</span>match<span class="token punctuation">(</span>return_rule<span class="token operator">=</span><span class="token boolean">True</span><span class="token punctuation">)</span>
            self<span class="token punctuation">.</span>request<span class="token punctuation">.</span>url_rule <span class="token operator">=</span> url_rule
        <span class="token keyword">except</span> HTTPException <span class="token keyword">as</span> e<span class="token punctuation">:</span>
            self<span class="token punctuation">.</span>request<span class="token punctuation">.</span>routing_exception <span class="token operator">=</span> e

    <span class="token keyword">def</span> <span class="token function">push</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token triple-quoted-string string">"""Binds the request context to the current context."""</span>
        <span class="token comment" spellcheck="true"># Before we push the request context we have to ensure that there</span>
        <span class="token comment" spellcheck="true"># is an application context.</span>
        app_ctx <span class="token operator">=</span> _app_ctx_stack<span class="token punctuation">.</span>top
        <span class="token keyword">if</span> app_ctx <span class="token keyword">is</span> None <span class="token operator">or</span> app_ctx<span class="token punctuation">.</span>app <span class="token operator">!=</span> self<span class="token punctuation">.</span>app<span class="token punctuation">:</span>
            app_ctx <span class="token operator">=</span> self<span class="token punctuation">.</span>app<span class="token punctuation">.</span>app_context<span class="token punctuation">(</span><span class="token punctuation">)</span>
            app_ctx<span class="token punctuation">.</span>push<span class="token punctuation">(</span><span class="token punctuation">)</span>
            self<span class="token punctuation">.</span>_implicit_app_ctx_stack<span class="token punctuation">.</span>append<span class="token punctuation">(</span>app_ctx<span class="token punctuation">)</span>
        <span class="token keyword">else</span><span class="token punctuation">:</span>
            self<span class="token punctuation">.</span>_implicit_app_ctx_stack<span class="token punctuation">.</span>append<span class="token punctuation">(</span>None<span class="token punctuation">)</span>

        _request_ctx_stack<span class="token punctuation">.</span>push<span class="token punctuation">(</span>self<span class="token punctuation">)</span>

        self<span class="token punctuation">.</span>session <span class="token operator">=</span> self<span class="token punctuation">.</span>app<span class="token punctuation">.</span>open_session<span class="token punctuation">(</span>self<span class="token punctuation">.</span>request<span class="token punctuation">)</span>
        <span class="token keyword">if</span> self<span class="token punctuation">.</span>session <span class="token keyword">is</span> None<span class="token punctuation">:</span>
            self<span class="token punctuation">.</span>session <span class="token operator">=</span> self<span class="token punctuation">.</span>app<span class="token punctuation">.</span>make_null_session<span class="token punctuation">(</span><span class="token punctuation">)</span>

    <span class="token keyword">def</span> <span class="token function">pop</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> exc<span class="token operator">=</span>_sentinel<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token triple-quoted-string string">"""Pops the request context and unbinds it by doing that.  This will
        also trigger the execution of functions registered by the
        :meth:`~flask.Flask.teardown_request` decorator.
        """</span>
        app_ctx <span class="token operator">=</span> self<span class="token punctuation">.</span>_implicit_app_ctx_stack<span class="token punctuation">.</span>pop<span class="token punctuation">(</span><span class="token punctuation">)</span>

        <span class="token keyword">try</span><span class="token punctuation">:</span>
            clear_request <span class="token operator">=</span> <span class="token boolean">False</span>
            <span class="token keyword">if</span> <span class="token operator">not</span> self<span class="token punctuation">.</span>_implicit_app_ctx_stack<span class="token punctuation">:</span>
                self<span class="token punctuation">.</span>app<span class="token punctuation">.</span>do_teardown_request<span class="token punctuation">(</span>exc<span class="token punctuation">)</span>

                request_close <span class="token operator">=</span> getattr<span class="token punctuation">(</span>self<span class="token punctuation">.</span>request<span class="token punctuation">,</span> <span class="token string">'close'</span><span class="token punctuation">,</span> None<span class="token punctuation">)</span>
                <span class="token keyword">if</span> request_close <span class="token keyword">is</span> <span class="token operator">not</span> None<span class="token punctuation">:</span>
                    request_close<span class="token punctuation">(</span><span class="token punctuation">)</span>
                clear_request <span class="token operator">=</span> <span class="token boolean">True</span>
        <span class="token keyword">finally</span><span class="token punctuation">:</span>
            rv <span class="token operator">=</span> _request_ctx_stack<span class="token punctuation">.</span>pop<span class="token punctuation">(</span><span class="token punctuation">)</span>

            <span class="token comment" spellcheck="true"># get rid of circular dependencies at the end of the request</span>
            <span class="token comment" spellcheck="true"># so that we don't require the GC to be active.</span>
            <span class="token keyword">if</span> clear_request<span class="token punctuation">:</span>
                rv<span class="token punctuation">.</span>request<span class="token punctuation">.</span>environ<span class="token punctuation">[</span><span class="token string">'werkzeug.request'</span><span class="token punctuation">]</span> <span class="token operator">=</span> None

            <span class="token comment" spellcheck="true"># Get rid of the app as well if necessary.</span>
            <span class="token keyword">if</span> app_ctx <span class="token keyword">is</span> <span class="token operator">not</span> None<span class="token punctuation">:</span>
                app_ctx<span class="token punctuation">.</span>pop<span class="token punctuation">(</span>exc<span class="token punctuation">)</span>

    <span class="token keyword">def</span> <span class="token function">auto_pop</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> exc<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token keyword">if</span> self<span class="token punctuation">.</span>request<span class="token punctuation">.</span>environ<span class="token punctuation">.</span>get<span class="token punctuation">(</span><span class="token string">'flask._preserve_context'</span><span class="token punctuation">)</span> <span class="token operator">or</span> \
           <span class="token punctuation">(</span>exc <span class="token keyword">is</span> <span class="token operator">not</span> None <span class="token operator">and</span> self<span class="token punctuation">.</span>app<span class="token punctuation">.</span>preserve_context_on_exception<span class="token punctuation">)</span><span class="token punctuation">:</span>
            self<span class="token punctuation">.</span>preserved <span class="token operator">=</span> <span class="token boolean">True</span>
            self<span class="token punctuation">.</span>_preserved_exc <span class="token operator">=</span> exc
        <span class="token keyword">else</span><span class="token punctuation">:</span>
            self<span class="token punctuation">.</span>pop<span class="token punctuation">(</span>exc<span class="token punctuation">)</span>

    <span class="token keyword">def</span> <span class="token function">__enter__</span><span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        self<span class="token punctuation">.</span>push<span class="token punctuation">(</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span> self

    <span class="token keyword">def</span> <span class="token function">__exit__</span><span class="token punctuation">(</span>self<span class="token punctuation">,</span> exc_type<span class="token punctuation">,</span> exc_value<span class="token punctuation">,</span> tb<span class="token punctuation">)</span><span class="token punctuation">:</span>
        self<span class="token punctuation">.</span>auto_pop<span class="token punctuation">(</span>exc_value<span class="token punctuation">)</span>
</code></pre>
<p>每个 request context 都保存了当前请求的信息，比如 request 对象和 app 对象。在初始化的最后，还调用了 <code>match_request</code> 实现了<a href="http://cizixs.com/2017/01/12/flask-insight-routing">路由的匹配逻辑</a>。</p>
<p> <code>push</code> 操作就是把该请求的 <code>ApplicationContext</code>（如果 <code>_app_ctx_stack</code> 栈顶不是当前请求所在 app ，需要创建新的 app context） 和 <code>RequestContext</code> 有关的信息保存到对应的栈上，压栈后还会保存 session 的信息； <code>pop</code> 则相反，把 request context 和 application context 出栈，做一些清理性的工作。</p>
<p>到这里，上下文的实现就比较清晰了：每次有请求过来的时候，flask 会先创建当前线程或者进程需要处理的两个重要上下文对象，把它们保存到隔离的栈里面，这样视图函数进行处理的时候就能直接从栈上获取这些信息。</p>
<p>NOTE：因为 app 实例只有一个，因此多个 <code>request</code> 共享了 <code>application context</code>。</p>
<p>到这里，关于 context 的实现和功能已经讲解得差不多了。还有两个疑惑没有解答。</p>
<ol>
<li>为什么要把 request context 和 application context 分开？每个请求不是都同时拥有这两个上下文信息吗？</li>
<li>为什么 request context 和 application context 都有实现成栈的结构？每个请求难道会出现多个 request context 或者 application context 吗？</li>
</ol>
<p>第一个答案是“灵活度”，第二个答案是“<a href="http://stackoverflow.com/a/20041823/1925083" target="_blank" rel="noopener">多 application</a>”。虽然在实际运行中，每个请求对应一个 request context 和一个 application context，但是在测试或者 python shell 中运行的时候，用户可以单独创建 request context 或者 application context，这种灵活度方便用户的不同的使用场景；而且栈可以让 redirect 更容易实现，一个处理函数可以从栈中获取重定向路径的多个请求信息。application 设计成栈也是类似，测试的时候可以添加多个上下文，另外一个原因是 flask 可以<a href="http://flask.pocoo.org/docs/0.12/patterns/appdispatch/#combining-applications" target="_blank" rel="noopener">多个 application 同时运行</a>:</p>
<pre class=" language-python"><code class="language-python"><span class="token keyword">from</span> werkzeug<span class="token punctuation">.</span>wsgi <span class="token keyword">import</span> DispatcherMiddleware
<span class="token keyword">from</span> frontend_app <span class="token keyword">import</span> application <span class="token keyword">as</span> frontend
<span class="token keyword">from</span> backend_app <span class="token keyword">import</span> application <span class="token keyword">as</span> backend

application <span class="token operator">=</span> DispatcherMiddleware<span class="token punctuation">(</span>frontend<span class="token punctuation">,</span> <span class="token punctuation">{</span>
    <span class="token string">'/backend'</span><span class="token punctuation">:</span>     backend
<span class="token punctuation">}</span><span class="token punctuation">)</span>
</code></pre>
<p>这个例子就是使用 <code>werkzeug</code> 的 <code>DispatcherMiddleware</code> 实现多个 app 的分发，这种情况下 <code>_app_ctx_stack</code> 栈里会出现两个 application context。</p>
<h2 id="Update：-为什么要用-LocalProxy"><a href="#Update：-为什么要用-LocalProxy" class="headerlink" title="Update： 为什么要用 LocalProxy"></a>Update： 为什么要用 LocalProxy</h2><p>写完这篇文章之后，收到有位读者的疑问：为什么要使用 <code>LocalProxy</code>？不使用 <code>LocalProxy</code> 直接访问 <code>LocalStack</code> 的对象会有什么问题吗？</p>
<p>这是个很好的问题，上面也确实没有很明确地给出这个答案。这里解释一下！</p>
<p>首先明确一点，<code>Local</code> 和 <code>LocalStack</code> 实现了不同线程/协程之间的数据隔离。在为什么用 <code>LocalStack</code> 而不是直接使用 <code>Local</code> 的时候，我们说过这是因为 flask 希望在测试或者开发的时候，允许多 app 、多 request 的情况。而 <code>LocalProxy</code> 也是因为这个才引入进来的！</p>
<p>我们拿 <code>current_app = LocalProxy(_find_app)</code>  来举例子。每次使用 <code>current_app</code> 的时候，他都会调用 <code>_find_app</code> 函数，然后对得到的变量进行操作。</p>
<p>如果直接使用 <code>current_app = _find_app()</code> 有什么区别呢？区别就在于，我们导入进来之后，<code>current_app</code> 就不会再变化了。如果有多 app 的情况，就会出现错误，比如：</p>
<pre class=" language-bash"><code class="language-bash">from flask <span class="token function">import</span> current_app

app <span class="token operator">=</span> create_app<span class="token punctuation">(</span><span class="token punctuation">)</span>
admin_app <span class="token operator">=</span> create_admin_app<span class="token punctuation">(</span><span class="token punctuation">)</span>

def do_something<span class="token punctuation">(</span><span class="token punctuation">)</span>:
    with app.app_context<span class="token punctuation">(</span><span class="token punctuation">)</span>:
        work_on<span class="token punctuation">(</span>current_app<span class="token punctuation">)</span>
        with admin_app.app_context<span class="token punctuation">(</span><span class="token punctuation">)</span>:
            work_on<span class="token punctuation">(</span>current_app<span class="token punctuation">)</span>
</code></pre>
<p>这里我们出现了嵌套的 app，每个 with 上下文都需要操作其对应的 <code>app</code>，如果不适用 <code>LocalProxy</code> 是做不到的。</p>
<p>对于 <code>request</code> 也是类似！但是这种情况真的很少发生，有必要费这么大的功夫增加这么多复杂度吗？</p>
<p>其实还有一个更大的问题，这个例子也可以看出来。比如我们知道 <code>current_app</code> 是动态的，因为它背后对应的栈会 push 和 pop 元素进去。那刚开始的时候，栈一定是空的，只有在 <code>with app.app_context()</code> 这句的时候，才把栈数据 push 进去。而如果不采用 <code>LocalProxy</code> 进行转发，那么在最上面导入 <code>from flask import current_app</code> 的时候，<code>current_app</code> 就是空的，因为这个时候还没有把数据 push 进去，后面调用的时候根本无法使用。</p>
<p>所以为什么需要 <code>LocalProxy</code> 呢？简单总结一句话：因为上下文保存的数据是保存在栈里的，并且会动态发生变化。如果不是动态地去访问，会造成数据访问异常。</p>
<h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul>
<li><a href="https://speakerdeck.com/mitsuhiko/advanced-flask-patterns" target="_blank" rel="noopener">advanced flask patterns by Armin Ronacher</a></li>
<li><a href="http://flask.pocoo.org/docs/0.12/appcontext/" target="_blank" rel="noopener">Flask doc: The application context</a></li>
<li><a href="https://blog.tonyseek.com/post/the-context-mechanism-of-flask/" target="_blank" rel="noopener">Flask 的 Context 机制</a></li>
</ul>

                </div>
            </section>
        </article>
    </div>
    
<nav class="pagination">
    
    
    <a class="prev-post" title="flask 源码解析：请求" href="/2017/01/18/flask-insight-request/">
        ← flask 源码解析：请求
    </a>
    
    <span class="prev-next-post">•</span>
    
    <a class="next-post" title="flask 源码解析：路由" href="/2017/01/12/flask-insight-routing/">
        flask 源码解析：路由 →
    </a>
    
    
</nav>

    <div class="inner">
    <!-- Begin Mailchimp Signup Form -->
    <link href="//cdn-images.mailchimp.com/embedcode/classic-10_7.css" rel="stylesheet" type="text/css">
    <style type="text/css">
    	#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; }
    	/* Add your own Mailchimp form style overrides in your site stylesheet or in this style block.
    	   We recommend moving this block and the preceding CSS link to the HEAD of your HTML file. */
    </style>
    <div id="mc_embed_signup">
    <form action="https://cizixs.us7.list-manage.com/subscribe/post?u=2d561b8dea52d73a2e05e6dcb&amp;id=5c710f135b" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
        <div id="mc_embed_signup_scroll">
    	<h2>订阅本博客，第一时间收到文章更新</h2>
    <div class="indicates-required"><span class="asterisk">*</span> indicates required</div>
    <div class="mc-field-group">
    	<label for="mce-EMAIL">邮件地址  <span class="asterisk">*</span>
    </label>
    	<input type="email" value="" name="EMAIL" class="required email" id="mce-EMAIL">
    </div>
    	<div id="mce-responses" class="clear">
    		<div class="response" id="mce-error-response" style="display:none"></div>
    		<div class="response" id="mce-success-response" style="display:none"></div>
    	</div>    <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
        <div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_2d561b8dea52d73a2e05e6dcb_5c710f135b" tabindex="-1" value=""></div>
        <div class="clear"><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
        </div>
    </form>
    </div>
    <script type='text/javascript' src='//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js'></script><script type='text/javascript'>(function($) {window.fnames = new Array(); window.ftypes = new Array();fnames[0]='EMAIL';ftypes[0]='email';}(jQuery));var $mcj = jQuery.noConflict(true);</script>
    <!--End mc_embed_signup-->
    </div>

    <div class="inner">
        <div id="disqus_thread"></div>
    </div>

    
</main>

<div class="t-g-control">
    <div class="gotop">
        <svg class="icon" width="32px" height="32px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M793.024 710.272a32 32 0 1 0 45.952-44.544l-310.304-320a32 32 0 0 0-46.4 0.48l-297.696 320a32 32 0 0 0 46.848 43.584l274.752-295.328 286.848 295.808z" fill="#8a8a8a" /></svg>
    </div>
    <div class="toc-control">
        <svg class="icon toc-icon" width="32px" height="32.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M779.776 480h-387.2a32 32 0 0 0 0 64h387.2a32 32 0 0 0 0-64M779.776 672h-387.2a32 32 0 0 0 0 64h387.2a32 32 0 0 0 0-64M256 288a32 32 0 1 0 0 64 32 32 0 0 0 0-64M392.576 352h387.2a32 32 0 0 0 0-64h-387.2a32 32 0 0 0 0 64M256 480a32 32 0 1 0 0 64 32 32 0 0 0 0-64M256 672a32 32 0 1 0 0 64 32 32 0 0 0 0-64" fill="#8a8a8a" /></svg>
        <svg class="icon toc-close" style="display: none;" width="32px" height="32.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M512 960c-247.039484 0-448-200.960516-448-448S264.960516 64 512 64 960 264.960516 960 512 759.039484 960 512 960zM512 128.287273c-211.584464 0-383.712727 172.128262-383.712727 383.712727 0 211.551781 172.128262 383.712727 383.712727 383.712727 211.551781 0 383.712727-172.159226 383.712727-383.712727C895.712727 300.415536 723.551781 128.287273 512 128.287273z" fill="#8a8a8a" /><path d="M557.05545 513.376159l138.367639-136.864185c12.576374-12.416396 12.672705-32.671738 0.25631-45.248112s-32.704421-12.672705-45.248112-0.25631l-138.560301 137.024163-136.447897-136.864185c-12.512727-12.512727-32.735385-12.576374-45.248112-0.063647-12.512727 12.480043-12.54369 32.735385-0.063647 45.248112l136.255235 136.671523-137.376804 135.904314c-12.576374 12.447359-12.672705 32.671738-0.25631 45.248112 6.271845 6.335493 14.496116 9.504099 22.751351 9.504099 8.12794 0 16.25588-3.103239 22.496761-9.247789l137.567746-136.064292 138.687596 139.136568c6.240882 6.271845 14.432469 9.407768 22.65674 9.407768 8.191587 0 16.352211-3.135923 22.591372-9.34412 12.512727-12.480043 12.54369-32.704421 0.063647-45.248112L557.05545 513.376159z" fill="#8a8a8a" /></svg>
    </div>
    <div class="gobottom">
        <svg class="icon" width="32px" height="32.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M231.424 346.208a32 32 0 0 0-46.848 43.584l297.696 320a32 32 0 0 0 46.4 0.48l310.304-320a32 32 0 1 0-45.952-44.544l-286.848 295.808-274.752-295.36z" fill="#8a8a8a" /></svg>
    </div>
</div>
<div class="toc-main" style="right: -100%">
    <div class="post-toc">
        <span>TOC</span>
        <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#上下文（application-context-和-request-context）"><span class="toc-text">上下文（application context 和 request context）</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Update：-为什么要用-LocalProxy"><span class="toc-text">Update： 为什么要用 LocalProxy</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#参考资料"><span class="toc-text">参考资料</span></a></li></ol>
    </div>
</div>



        

<aside class="read-next outer">
    <div class="inner">
        <div class="read-next-feed">
            
            

<article class="read-next-card"  style="background-image: url(https://cizixs-blog.oss-cn-beijing.aliyuncs.com/006tNc79ly1g1qxcn9ft3j318w0txdo6.jpg)"  >
  <header class="read-next-card-header">
    <small class="read-next-card-header-sitetitle">&mdash; Cizixs Write Here &mdash;</small>
    <h3 class="read-next-card-header-title">Recent Posts</h3>
  </header>
  <div class="read-next-divider">
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
      <path d="M13 14.5s2 3 5 3 5.5-2.463 5.5-5.5S21 6.5 18 6.5c-5 0-7 11-12 11C2.962 17.5.5 15.037.5 12S3 6.5 6 6.5s4.5 3.5 4.5 3.5"/>
    </svg>
  </div>
  <div class="read-next-card-content">
    <ul>
      
      
      
      <li>
        <a href="/2018/08/26/what-is-istio/">什么是 istio</a>
      </li>
      
      
      
      <li>
        <a href="/2018/08/25/knative-serverless-platform/">serverless 平台 knative 简介</a>
      </li>
      
      
      
      <li>
        <a href="/2018/06/25/kubernetes-resource-management/">kubernetes 资源管理概述</a>
      </li>
      
      
      
      <li>
        <a href="/2018/01/24/use-prometheus-and-grafana-to-monitor-linux-machine/">使用 promethues 和 grafana 监控自己的 linux 机器</a>
      </li>
      
      
      
      <li>
        <a href="/2018/01/13/linux-udp-packet-drop-debug/">linux 系统 UDP 丢包问题分析思路</a>
      </li>
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
    </ul>
  </div>
  <footer class="read-next-card-footer">
    <a href="/archives">  MORE  → </a>
  </footer>
</article>


            
            
            
        </div>
    </div>
</aside>


<footer class="site-footer outer">

	<div class="site-footer-content inner">
		<section class="copyright">
			<a href="/" title="Cizixs Write Here">Cizixs Write Here</a>
			&copy; 2019
		</section>
		<nav class="site-footer-nav">
			
            <a href="https://hexo.io" title="Hexo" target="_blank" rel="noopener">Hexo</a>
            <a href="https://github.com/xzhih/hexo-theme-casper" title="Casper" target="_blank" rel="noopener">Casper</a>
        </nav>
    </div>
</footer>






<div class="floating-header" >
	<div class="floating-header-logo">
        <a href="/" title="Cizixs Write Here">
			
                <img src="https://cizixs-blog.oss-cn-beijing.aliyuncs.com/006tNc79ly1g1qxfovpzyj30740743yg.jpg" alt="Cizixs Write Here icon" />
			
            <span>Cizixs Write Here</span>
        </a>
    </div>
    <span class="floating-header-divider">&mdash;</span>
    <div class="floating-header-title">flask 源码解析：上下文</div>
    <progress class="progress" value="0">
        <div class="progress-container">
            <span class="progress-bar"></span>
        </div>
    </progress>
</div>
<script>
   $(document).ready(function () {
    var progressBar = document.querySelector('progress');
    var header = document.querySelector('.floating-header');
    var title = document.querySelector('.post-full-title');
    var lastScrollY = window.scrollY;
    var lastWindowHeight = window.innerHeight;
    var lastDocumentHeight = $(document).height();
    var ticking = false;

    function onScroll() {
        lastScrollY = window.scrollY;
        requestTick();
    }
    function requestTick() {
        if (!ticking) {
            requestAnimationFrame(update);
        }
        ticking = true;
    }
    function update() {
        var rect = title.getBoundingClientRect();
        var trigger = rect.top + window.scrollY;
        var triggerOffset = title.offsetHeight + 35;
        var progressMax = lastDocumentHeight - lastWindowHeight;
            // show/hide floating header
            if (lastScrollY >= trigger + triggerOffset) {
                header.classList.add('floating-active');
            } else {
                header.classList.remove('floating-active');
            }
            progressBar.setAttribute('max', progressMax);
            progressBar.setAttribute('value', lastScrollY);
            ticking = false;
        }

        window.addEventListener('scroll', onScroll, {passive: true});
        update();

        // TOC
        var width = $('.toc-main').width();
        $('.toc-control').click(function () {
            if ($('.t-g-control').css('width')=="50px") {
                if ($('.t-g-control').css('right')=="0px") {
                    $('.t-g-control').animate({right: width}, "slow");
                    $('.toc-main').animate({right: 0}, "slow");
                    toc_icon()
                } else {
                    $('.t-g-control').animate({right: 0}, "slow");
                    $('.toc-main').animate({right: -width}, "slow");
                    toc_icon()
                }
            } else {
                if ($('.toc-main').css('right')=="0px") {
                    $('.toc-main').slideToggle("fast", toc_icon());
                } else {
                    $('.toc-main').css('right', '0px');
                    toc_icon()
                }
            }
        })

        function toc_icon() {
            if ($('.toc-icon').css('display')=="none") {
                $('.toc-close').hide();
                $('.toc-icon').show();
            } else {
                $('.toc-icon').hide();
                $('.toc-close').show();
            }
        }

        $('.gotop').click(function(){
            $('html,body').animate({scrollTop:$('.post-full-header').offset().top}, 800);
        });
        $('.gobottom').click(function () {
            $('html,body').animate({scrollTop:$('.pagination').offset().top}, 800);
        });

        // highlight
        // https://highlightjs.org
        $('pre code').each(function(i, block) {
            hljs.highlightBlock(block);
        });
        $('td.code').each(function(i, block) {
            hljs.highlightBlock(block);
        });

        console.log("this theme is from https://github.com/xzhih/hexo-theme-casper")
    });
</script>



<link rel="stylesheet" href="https://cdn.staticfile.org/lightgallery/1.3.9/css/lightgallery.min.css">



<script src="https://cdn.staticfile.org/lightgallery/1.3.9/js/lightgallery.min.js"></script>


<script>
	$(function () {
		var postImg = $('#lightgallery').find('img');
		postImg.addClass('post-img');
		postImg.each(function () {
			var imgSrc = $(this).attr('src');
			$(this).attr('data-src', imgSrc);
		});
		$('#lightgallery').lightGallery({selector: '.post-img'});
	});
</script>



<script>

/**
*  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
*  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables*/

var disqus_config = function () {
this.page.url = 'http://cizixs.com/2017/01/13/flask-insight-context/';  // Replace PAGE_URL with your page's canonical URL variable
this.page.identifier = 'http://cizixs.com/2017/01/13/flask-insight-context/'; // Replace PAGE_IDENTIFIER with your page's unique identifier variable
};

(function() { // DON'T EDIT BELOW THIS LINE
var d = document, s = d.createElement('script');
s.src = 'https://cizixs.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
                            


    </div>
</body>
</html>
